package com.example.idemfactor.sdk.aspect;

import com.example.idemfactor.sdk.annotations.Idemfactor;
import com.example.idemfactor.sdk.exception.IdemfactorException;
import com.example.idemfactor.sdk.interfaces.IdemFallback;
import com.example.idemfactor.sdk.interfaces.IdemKeyGenerator;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;

public abstract class IdemAspectSupport implements BeanFactoryAware, InitializingBean {

    @Nullable
    private BeanFactory beanFactory;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    @Override
    public void afterPropertiesSet() throws Exception {

    }

    @Nullable
    protected Object execute(Object invoke, Object target, Method method, Object[] args) {
        // 获取注解信息
        Idemfactor annotation = method.getAnnotation(Idemfactor.class);
        StringRedisTemplate stringRedisTemplate =this.beanFactory.getBean(StringRedisTemplate.class);
        String key = getRedisKey(method,args,target);

        // 判断redis中是否存在key
        if (stringRedisTemplate.hasKey(key)) {
            // 如果未设置fallback回调方法，则抛出异常
            if("void".equals(annotation.fallback().getName())){
                throw new IdemfactorException(annotation.message(),500);
            }
            // 获取当前方法返回值类型(当返回值类型为空,这里不做特殊处理)
            String typeName = method.getReturnType().getTypeName();
            // 如果判断fallback类型与真实接口返回值类型是否相等
            //if(!typeName.equals(getInterfacesType(annotation.fallback()))){
            if(!getInterfacesType(annotation.fallback()).contains(typeName)){
                throw new IdemfactorException("回调返回类型和实际方法返回类型不匹配",500);
            }
            IdemFallback idemFallback = (IdemFallback) beanFactory.getBean(annotation.fallback());
            return idemFallback.callback();
        }

        // 不存在往redis中写入key值
        stringRedisTemplate.opsForValue().set(key, "1", annotation.expireTime(), TimeUnit.SECONDS);

        //默认不删除 如果修改则使用修改后的配置
        if (annotation.delKey()) {
            stringRedisTemplate.delete(key);
        }

        return invoke;
    }

    /**
     * 获取唯一接口唯一的幂等redis的key
     */
    protected String getRedisKey(Method method,Object[] args,Object target){
        //获取注解信息
        Idemfactor annotation = method.getAnnotation(Idemfactor.class);

        // 判断注解是否有 keyGenerator 属性
        if (!StringUtils.isEmpty(annotation.keyGenerator())){
            IdemKeyGenerator idemKeyGenerator =
                    (IdemKeyGenerator) this.beanFactory.getBean(annotation.keyGenerator());
            return idemKeyGenerator.generate(target,method,args);
        }

        // 判断注解是否有 key 属性
        if (!StringUtils.isEmpty(annotation.key())) {
            return parserAnno(annotation.key(),getParameterName(method),args);
        }

        // 默认key = 全限定类名+方法名
        return target.getClass().getName()+"."+method.getName();
    }

    /**
     *
     * 解析SpEL表达式
     *
     * @param primaryKey SpEL表达式
     * @param names 参数名列表
     * @param args  参数值列表
     * @return
     */
    protected String parserAnno(String primaryKey, String[] names , Object[] args){
        ExpressionParser parser=new SpelExpressionParser();
        EvaluationContext ctx = new StandardEvaluationContext();

        //获取方法的参数名和参数值
        List<String> paramNameList = Arrays.asList(names);
        List<Object> paramList = Arrays.asList(args);

        //将方法的参数名和参数值一一对应的放入上下文中
        for (int i = 0; i < paramNameList.size(); i++) {
            ctx.setVariable(paramNameList.get(i), paramList.get(i));
        }

        //解析SpEL表达式获取值
        String value = parser.parseExpression(primaryKey).getValue(ctx).toString();
        return value;
    }

    /**
     *
     * 获取在接口上定义的泛型类型
     *
     * @param clazz class
     * @return
     */
    protected String getInterfacesType(Class clazz){
        String name = "";
        Type[] types = clazz.getGenericInterfaces();
        for (Type t : types) {
            Type[] genericType2 = ((ParameterizedType) t).getActualTypeArguments();
            for (Type t2 : genericType2) {
                name = t2.getTypeName();
            }
        }
        return name;
    }


    /**
     *  获取真实参数名称而不是代理类的参数名称
     */
    private String[] getParameterName(Method specificMethod) {

        // Project needs to be build with a -g (debug) option to contain information about parameter names.
        ParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
        String[] parameterName = discoverer.getParameterNames(specificMethod);
        return parameterName;


    }
}
